import wx
import wx.wizard as wzrd
import wx.animate as anim

from clipwidget import ClipWidget

import model
from math import pi, cos, sin



class ImageView(wx.Control):

    BCKGND_RECT_SIZE = 16

    def __init__(self, bitmap=None, *args, **argv):
        super(ImageView, self).__init__(*args, **argv)
        self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
        self.bitmap = bitmap
        self.Bind(wx.EVT_PAINT, self.OnPaintImageView)

    def getBitmap(self):
        return self.bitmap


    def setBitmap(self, bitmap):
        self.bitmap = bitmap
        self.Refresh()


    def getZoomAndOffs(self):
        dcWidth, dcHeight = self.GetClientSizeTuple()
        zoomX = dcWidth / float(self.bitmap.GetWidth())
        zoomY = dcHeight / float(self.bitmap.GetHeight())
        if zoomX < zoomY:
            zoom = zoomX
            xOffs, yOffs = 0, (dcHeight/zoom - self.bitmap.GetHeight()) / 2
        else:
            zoom = zoomY
            xOffs, yOffs = (dcWidth/zoom - self.bitmap.GetWidth()) / 2, 0
        return zoom, (xOffs, yOffs)


    def OnPaintImageView(self, ev):
        dc = wx.PaintDC(self)
        dcWidth, dcHeight = dc.GetSizeTuple()
        self.drawBackground(dc, dcWidth, dcHeight)
        self.drawImage(dc, dcWidth, dcHeight)


    def drawBackground(self, dc, dcWidth, dcHeight):
        # draw background
        dc.SetUserScale(1,1)
        for x in range(dcWidth/self.BCKGND_RECT_SIZE+1):
            for y in range(dcHeight/self.BCKGND_RECT_SIZE+1):
                if (x%2) ^ (y%2):  # if either x or y is odd
                    dc.SetBrush(wx.LIGHT_GREY_BRUSH)
                    dc.SetPen(wx.LIGHT_GREY_PEN)
                else:
                    dc.SetBrush(wx.WHITE_BRUSH)
                    dc.SetPen(wx.WHITE_PEN)
                dc.DrawRectangle(x*self.BCKGND_RECT_SIZE,
                                 y*self.BCKGND_RECT_SIZE,
                                 self.BCKGND_RECT_SIZE,
                                 self.BCKGND_RECT_SIZE)


    def drawImage(self, dc, dcWidth, dcHeight):
         # draw scaled bodypart image
        if self.bitmap == None:
            return

        zoom, (xOffs, yOffs) = self.getZoomAndOffs()
        dc.SetUserScale(zoom, zoom)
        dc.DrawBitmap(self.bitmap, xOffs, yOffs)



class BodyPartWizard(wzrd.Wizard):

    class ImageSelectPage(wzrd.WizardPageSimple):

        def __init__(self, parent):
            super(BodyPartWizard.ImageSelectPage, self).__init__(parent)
            self.Bind(wzrd.EVT_WIZARD_PAGE_CHANGED, self.OnEnterPage)
            self.Bind(wzrd.EVT_WIZARD_PAGE_CHANGING, self.OnExitPage)
            self.parent = parent
            self.image = None
            
            sizer = wx.BoxSizer(wx.VERTICAL)
            self.SetSizer(sizer)
            
            title = wx.StaticText(self, -1, "Photo That Contains BodyPart")
            sizer.Add(title, 0, wx.ALIGN_LEFT|wx.RIGHT|wx.LEFT|wx.TOP, 5)

            self.imageView = ImageView(None, self, -1,
                                       style=wx.BORDER_SUNKEN)
            sizer.Add(self.imageView, 1, wx.EXPAND|wx.ALL, 5)

            sizer2 = wx.BoxSizer(wx.HORIZONTAL)
            sizer.Add(sizer2, 0, wx.ALIGN_LEFT, 0)
            
            btnFromClipboard = wx.Button(self, -1, "Copy From Clipboard")
            self.Bind(wx.EVT_BUTTON, self.OnCopyFromClipboard, btnFromClipboard)
            sizer2.Add(btnFromClipboard, 0, wx.ALL, 5)
            
            btnFromFile = wx.Button(self, -1, "Load From File...")
            self.Bind(wx.EVT_BUTTON, self.OnLoadFromFile, btnFromFile)
            sizer2.Add(btnFromFile, 0, wx.ALL, 5)


        def OnExitPage(self, ev):
            if not ev.Direction:
                return
            
            if self.image == None:
                wx.MessageBox("No Photo has been selected", "Error")
                ev.Veto()
            else:
                if self.parent.bodyPart:
                    self.parent.bodyPart.setBaseImage(self.image)
                else:
                    self.parent.bodyPart = model.BodyPart(self.image)
                

        def OnEnterPage(self, ev):
            if self.parent.bodyPart == None:
                self.image = None
                bitmap = None
            else:
                self.image = self.parent.bodyPart.getBaseImage()
                bitmap = wx.BitmapFromImage(self.image)
            self.imageView.setBitmap(bitmap)


        def OnCopyFromClipboard(self, ev):
            if wx.TheClipboard.Open():
                try:
                    # get bitmap from clipboard
                    if not wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_BITMAP)):
                        raise StandardError("No Bitmap in Clipboard")
                    dataObject = wx.BitmapDataObject()
                    if not wx.TheClipboard.GetData(dataObject):
                        raise StandardError("Failed to get Bitmap from Clipboard")
                    
                except StandardError, e:
                    wx.MessageBox(str(e), "Error", style=wx.ICON_ERROR)
                    
                else:
                    bitmap = dataObject.GetBitmap()
                    self.image = wx.ImageFromBitmap(bitmap)
                    self.imageView.setBitmap(bitmap)
                    
                finally:
                    # close clipboard again
                    wx.TheClipboard.Close()        


        def OnLoadFromFile(self, ev):
            fileTypes = ("Photos only (JPEG,BMP,PNG)|*.jpg;*.bmp;*.png|"
                         "All files|*.*")
            dlg = wx.FileDialog(self,
                                message="Choose a Photo",
                                wildcard=fileTypes,
                                style=wx.OPEN)

            if dlg.ShowModal() == wx.ID_OK:
                self.image = wx.Image(dlg.GetPath())
                if not self.image.IsOk():
                    wx.MessageBox("Failed to load Photo (invalid file format?)",
                                  "Error", style=wx.ICON_ERROR)
                else:
                    self.imageView.setBitmap(wx.BitmapFromImage(self.image))


    class ImageExtractPage(wzrd.WizardPageSimple):

        def __init__(self, parent):
            super(BodyPartWizard.ImageExtractPage, self).__init__(parent)
            self.Bind(wzrd.EVT_WIZARD_PAGE_CHANGED, self.OnEnterPage)
            self.Bind(wzrd.EVT_WIZARD_PAGE_CHANGING, self.OnExitPage)
            self.parent = parent
            
            sizer = wx.BoxSizer(wx.VERTICAL)
            self.SetSizer(sizer)
            
            title = wx.StaticText(self, -1, "Extract BodyPart from Photo")
            sizer.Add(title, 0, wx.ALIGN_LEFT|wx.RIGHT|wx.LEFT|wx.TOP, 5)

            self.clipWidget = ClipWidget(self, None, style=wx.BORDER_SUNKEN)
            sizer.Add(self.clipWidget, 1, wx.EXPAND|wx.ALL, 5)

            sizerTools = wx.BoxSizer(wx.HORIZONTAL)
            sizer.Add(sizerTools, 0, wx.EXPAND|wx.ALL, 5)
            
            btnDeleteSelection = wx.Button(self, -1, "Delete Selection")
            self.Bind(wx.EVT_BUTTON, self.OnDeleteSelection, btnDeleteSelection)
            sizerTools.Add(btnDeleteSelection, 0, 0, 0)
            sizerTools.AddStretchSpacer(1)
            
            btnZoomOut = wx.Button(self, -1, "Zoom Out")
            self.Bind(wx.EVT_BUTTON, self.OnZoomOut, btnZoomOut)
            sizerTools.Add(btnZoomOut, 0, 0, 0)
            
            btnZoomIn = wx.Button(self, -1, "Zoom In")
            self.Bind(wx.EVT_BUTTON, self.OnZoomIn, btnZoomIn)
            sizerTools.Add(btnZoomIn, 0, 0, 0)


        def OnZoomOut(self, ev):
            self.clipWidget.zoomOut()


        def OnZoomIn(self, ev):
            self.clipWidget.zoomIn()


        def OnDeleteSelection(self, ev):
            self.clipWidget.setPolygon(None)
            

        def OnEnterPage(self, ev):
            self.clipWidget.setImage(self.parent.bodyPart.getBaseImage())


        def OnExitPage(self, ev):
            if not ev.Direction:
                return
            if not self.clipWidget.closed:
                wx.MessageBox("Missing Selection Of BodyPart", "Error")
                ev.Veto()
                return
            
            self.parent.bodyPart.setExtractPolygon(self.clipWidget.getPolygon())


    class LayoutImagePage(wzrd.WizardPageSimple):

        class LayoutImageView(ImageView):

            ZOOM_STEPS = 4.0

            def __init__(self, center, mirrored, *args, **argv):
                super(BodyPartWizard.LayoutImagePage.LayoutImageView,
                      self).__init__(*args, **argv)
                self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouseEvent)
                self.SetCursor(wx.CROSS_CURSOR)
                self.center = center
                self.mirrored = mirrored
                self.angle = 0.0
                self.zoom = 1.0
                

            def setImage(self, image):
                super(BodyPartWizard.LayoutImagePage.LayoutImageView,
                      self).setImage(image)


            def setMirrored(self, mirrored):
                self.mirrored = mirrored
                self.angle = -self.angle
                self.Refresh()


            def getMirrored(self):
                return self.mirrored


            def setCenter(self, center):
                self.center = center
                self.Refresh()

                
            def getCenter(self):
                return self.center


            def setAngle(self, angle):
                self.angle = angle
                self.Refresh()


            def getAngle(self):
                return self.angle


            def setZoom(self, zoom):
                self.zoom = zoom
                self.Refresh()


            def getZoom(self):
                return self.zoom


            def OnMouseEvent(self, ev):
                if ev.LeftDown():
                    self.CaptureMouse()
                elif ev.LeftUp() and self.HasCapture():
                    self.ReleaseMouse()
                elif ev.GetWheelRotation() != 0:
                    if ev.GetWheelRotation() < 0:
                        factor = 0.9 **-ev.GetWheelRotation()/ev.GetWheelDelta()
                    else:
                        factor = 1.1 ** ev.GetWheelRotation()/ev.GetWheelDelta()
                    self.setZoom(self.getZoom() * factor)
                    
                    
                if ev.LeftIsDown():
                    zoom, (xOffs, yOffs) = self.getZoomAndOffs()
                    x = ev.GetX()/zoom - xOffs
                    y = ev.GetY()/zoom - yOffs
                    
                    a = -self.angle
                    dx = x - self.getBitmap().GetWidth()/2
                    dy = y - self.getBitmap().GetHeight()/2
                    x = self.getBitmap().GetWidth()/2 + dx*cos(a) - dy*sin(a)
                    y = self.getBitmap().GetHeight()/2 + dx*sin(a) + dy*cos(a)
                    if self.mirrored:
                        x = self.image.GetWidth() - x
                    self.setCenter( (x,y) )


            def getUnitZoomAndOffs(self):
                uWidth = self.getBitmap().GetWidth() * self.zoom
                uHeight = self.getBitmap().GetHeight() * self.zoom
                dcWidth, dcHeight = self.GetClientSizeTuple()
                zoom = min(dcWidth, dcHeight)*0.6
                while uWidth*zoom > dcWidth or uHeight*zoom > dcHeight:
                    zoom /= self.ZOOM_STEPS
                xOffs = dcWidth/zoom / 2 - 0.5
                yOffs = dcHeight/zoom / 2 - 0.5
                return zoom, (xOffs, yOffs)
                

            def getZoomAndOffs(self):
                uZoom, (uxOffs, uyOffs) = self.getUnitZoomAndOffs()
                zoom = self.zoom * uZoom
                xOffs = (uxOffs+0.5) / self.zoom - self.getBitmap().GetWidth()/2
                yOffs = (uyOffs+0.5) / self.zoom -self.getBitmap().GetHeight()/2
                return zoom, (xOffs, yOffs)


            def OnPaintImageView(self, ev):
                dc = wx.PaintDC(self)
                dcWidth, dcHeight = dc.GetSizeTuple()
                self.drawBackground(dc, dcWidth, dcHeight)
                self.drawImage(dc, dcWidth, dcHeight)
                self.drawUnitGrid(dc, dcWidth, dcHeight)
                self.drawCenterCross(dc, dcWidth, dcHeight)


            def drawImage(self, dc, dcWidth, dcHeight):
                zoom, (xOffs, yOffs) = self.getZoomAndOffs()
                
                # get image and rotate it
                bitmap = self.getBitmap()
                gc = wx.GraphicsContext.Create(dc)
                gc.Translate(dcWidth/2, dcHeight/2)
                gc.Rotate(self.angle)
                gc.Scale(-zoom if self.mirrored else zoom, zoom)
                gc.DrawBitmap(bitmap,
                              -bitmap.GetWidth()/2, -bitmap.GetHeight()/2,
                              bitmap.GetWidth(), bitmap.GetHeight() )
  

            def drawUnitGrid(self, dc, dcWidth, dcHeight):
                zoom, (xOffs, yOffs) = self.getUnitZoomAndOffs()
                if zoom < 2.0: return
                
                dc.SetPen(wx.GREY_PEN)
                dc.SetUserScale(1, 1)
                for x in range(-int(xOffs), int(2 + 2*xOffs)):
                    dc.DrawLine((xOffs+x)*zoom, 0, (xOffs+x)*zoom, dcHeight)
                for y in range(-int(yOffs), int(2 + 2*yOffs)):
                    dc.DrawLine(0, (yOffs+y)*zoom, dcWidth, (yOffs+y)*zoom)

                
            def drawCenterCross(self, dc, dcWidth, dcHeight):
                zoom, (xOffs, yOffs) = self.getZoomAndOffs()
                x, y = self.center
                if self.mirrored:
                    x = self.getBitmap().GetWidth() - x
                a = self.angle
                dx = x - self.getBitmap().GetWidth()/2
                dy = y - self.getBitmap().GetHeight()/2
                x = self.getBitmap().GetWidth()/2 + dx*cos(a) - dy*sin(a)
                y = self.getBitmap().GetHeight()/2 + dx*sin(a) + dy*cos(a)
                    
                realX = (x + xOffs) * zoom
                realY = (y + yOffs) * zoom
                dc.SetUserScale(1,1)
                dc.SetPen(wx.RED_PEN)
                dc.DrawLine(0, realY, dcWidth, realY)
                dc.DrawLine(realX, 0, realX, dcHeight)
                dc.SetPen(wx.BLACK_DASHED_PEN)
                dc.DrawLine(0, realY, dcWidth, realY)
                dc.DrawLine(realX, 0, realX, dcHeight)
                
                        

        def __init__(self, parent):
            super(BodyPartWizard.LayoutImagePage, self).__init__(parent)
            self.Bind(wzrd.EVT_WIZARD_PAGE_CHANGED, self.OnEnterPage)
            self.Bind(wzrd.EVT_WIZARD_PAGE_CHANGING, self.OnExitPage)
            self.parent = parent
            
            sizer = wx.BoxSizer(wx.VERTICAL)
            self.SetSizer(sizer)
            
            title = wx.StaticText(self, -1, "Rotation Center Of Body Part")
            sizer.Add(title, 0, wx.ALIGN_LEFT|wx.RIGHT|wx.LEFT|wx.TOP, 5)
            self.imageView = self.LayoutImageView( (0,0), False, None,
                                                   self, -1,
                                                   style=wx.BORDER_SUNKEN)
            sizer.Add(self.imageView, 1, wx.EXPAND|wx.ALL, 5)

            self.cbxMirrored = wx.CheckBox(self, -1, "Mirrored")
            self.Bind(wx.EVT_CHECKBOX, self.OnMirrorImage, self.cbxMirrored)
            sizer.Add(self.cbxMirrored, 0, wx.ALIGN_LEFT|wx.ALL, 5)

            title = wx.StaticText(self, -1, "Base Rotation Of Body Part")
            sizer.Add(title, 0, wx.ALIGN_LEFT|wx.RIGHT|wx.LEFT|wx.TOP, 5)
            self.sliderRotation = wx.Slider(self, -1, 500, 0, 1000,
                                            style=wx.SL_AUTOTICKS)
            self.sliderRotation.SetTickFreq(20, 1)
            self.sliderRotation.Bind(wx.EVT_SCROLL, self.OnChangeRotation)
            sizer.Add(self.sliderRotation, 0, wx.EXPAND|wx.ALL, 5)

            title = wx.StaticText(self, -1, "Base Zoom Of Body Part")
            sizer.Add(title, 0, wx.ALIGN_LEFT|wx.RIGHT|wx.LEFT|wx.TOP, 5)
            sizerZoom = wx.BoxSizer(wx.HORIZONTAL)
            sizer.Add(sizerZoom, 0, wx.EXPAND|wx.ALL, 5)
            btnZoomSub100 = wx.Button(self, -1, "-100%")
            self.Bind(wx.EVT_BUTTON, self.OnChangeZoom, btnZoomSub100)
            sizerZoom.Add(btnZoomSub100, 0, 0, 0)
            btnZoomSub10 = wx.Button(self, -1, "-10%")
            self.Bind(wx.EVT_BUTTON, self.OnChangeZoom, btnZoomSub10)
            sizerZoom.Add(btnZoomSub10, 0, 0, 0)
            btnZoomSub1 = wx.Button(self, -1, "-1%")
            self.Bind(wx.EVT_BUTTON, self.OnChangeZoom, btnZoomSub1)
            sizerZoom.Add(btnZoomSub1, 0, 0, 0)
            sizerZoom.AddStretchSpacer(1)
            btnZoomReset = wx.Button(self, -1, "Reset")
            self.Bind(wx.EVT_BUTTON, self.OnResetZoom, btnZoomReset)
            sizerZoom.Add(btnZoomReset, 0, 0, 0)
            sizerZoom.AddStretchSpacer(1)
            btnZoomAdd1 = wx.Button(self, -1, "+1%")
            self.Bind(wx.EVT_BUTTON, self.OnChangeZoom, btnZoomAdd1)
            sizerZoom.Add(btnZoomAdd1, 0, 0, 0)
            btnZoomAdd10 = wx.Button(self, -1, "+10%")
            self.Bind(wx.EVT_BUTTON, self.OnChangeZoom, btnZoomAdd10)
            sizerZoom.Add(btnZoomAdd10, 0, 0, 0)
            btnZoomAdd100 = wx.Button(self, -1, "+100%")
            self.Bind(wx.EVT_BUTTON, self.OnChangeZoom, btnZoomAdd100)
            sizerZoom.Add(btnZoomAdd100, 0, 0, 0)
            self.zoomIdMap = {
                btnZoomAdd100.GetId(): 2.0,
                btnZoomAdd10.GetId(): 1.1,
                btnZoomAdd1.GetId(): 1.01,
                btnZoomSub1.GetId(): 0.99,
                btnZoomSub10.GetId(): 0.90,
                btnZoomSub100.GetId(): 0.5 }
            self.zoomResetValue = 1.0


        def updateControls(self):
            self.cbxMirrored.SetValue(self.imageView.getMirrored())
            angle = self.imageView.getAngle() / pi * 500.0 + 500
            self.sliderRotation.SetValue(angle)


        def OnMirrorImage(self, ev):
            self.imageView.setMirrored(self.cbxMirrored.GetValue())
            self.updateControls()


        def OnChangeRotation(self, ev):
            angle = (self.sliderRotation.GetValue() - 500) / 500.0 * pi
            self.imageView.setAngle(angle)


        def OnChangeZoom(self, ev):
            zoom = self.imageView.getZoom() * self.zoomIdMap[ev.GetId()]
            self.imageView.setZoom(zoom)


        def OnResetZoom(self, ev):
            self.imageView.setZoom(self.parent.bodyPart.getZoom())


        def OnEnterPage(self, ev):
            self.imageView.setBitmap(self.parent.bodyPart.getCachedBitmap())
            self.imageView.setMirrored(self.parent.bodyPart.getMirrored())
            self.imageView.setCenter(self.parent.bodyPart.getCenter())
            self.imageView.setAngle(self.parent.bodyPart.getAngle())
            self.imageView.setZoom(self.parent.bodyPart.getZoom())
            self.updateControls()

                
        def OnExitPage(self, ev):
            self.parent.bodyPart.setMirrored(self.imageView.getMirrored())
            self.parent.bodyPart.setCenter(self.imageView.getCenter())
            self.parent.bodyPart.setAngle(self.imageView.getAngle())
            self.parent.bodyPart.setZoom(self.imageView.getZoom())



    def __init__(self, parent=None):
        super(BodyPartWizard, self).__init__(parent, -1, "Body Part Editor")
        self.bodyPart = None

        self.pageSelectImage = self.ImageSelectPage(self)        
        self.pageExtractImage = self.ImageExtractPage(self)
        self.pageLayoutImage = self.LayoutImagePage(self)
        wzrd.WizardPageSimple_Chain(self.pageSelectImage, self.pageExtractImage)
        wzrd.WizardPageSimple_Chain(self.pageExtractImage, self.pageLayoutImage)
        self.GetPageAreaSizer().Add(self.pageSelectImage)
        self.GetPageAreaSizer().SetMinSize( (800, 600) )


    def EditBodyPart(self):
        self.RunWizard(self.pageSelectImage)
        


if __name__ == "__main__":
    app = wx.App(redirect=False)
    wizard = BodyPartWizard()
    wizard.EditBodyPart()
